package hbasecli.type.example;

import java.text.DecimalFormat;

import hbasecli.type.DataType;
import org.apache.hadoop.hbase.util.Bytes;

import static org.springframework.util.StringUtils.hasText;
import static org.springframework.util.StringUtils.trimAllWhitespace;

public class Z2 implements DataType {

    private static final DecimalFormat decimalFormat = new DecimalFormat("#.00000");

    private static double parseDouble(String s) {
        if (!hasText(s)) {
            return 0.0;
        }
        double value = 0.0;
        try {
            value = Double.parseDouble(s);
        } catch (NumberFormatException ignore) {
            //
        }
        return value;
    }

    @Override
    public String name() {
        return "Z2Index";
    }

    @Override
    public String bytesToString(byte[] bytes) {
        if (bytes == null || bytes.length < 8) {
            return "";
        } else {
            long z = Bytes.toLong(bytes, 0, 8);
            double[] values = invert(z);
            return decimalFormat.format(values[0]) + "," + decimalFormat.format(values[1]);
        }
    }

    @Override
    public byte[] stringToBytes(String string) {
        String[] strings = string.replace("，", ",").split(",");
        if (strings.length < 2) {
            return new byte[]{0, 0, 0, 0, 0, 0, 0, 0};
        }
        double value0 = parseDouble(trimAllWhitespace(strings[0]));
        double value1 = parseDouble(trimAllWhitespace(strings[1]));
        long z = index(value0, value1);
        return Bytes.toBytes(z);
    }

    public int normalize(double value, double minValue, double maxValue, int precision) {
        if (value >= maxValue) {
            return (int) ((1L << precision) - 1);
        }
        long bins = 1L << precision;
        double k = bins / (maxValue - minValue);
        return (int) Math.floor((value - minValue) * k);
    }

    public double denormalize(int normalizedValue, double minRawValue, double maxRawValue, int precision) {
        int maxNormalizeValue = (int) ((1L << precision) - 1);
        normalizedValue = Math.min(normalizedValue, maxNormalizeValue);
        long bins = 1L << precision;
        double k = (maxRawValue - minRawValue) / bins;
        // TODO
        return minRawValue + ((double) normalizedValue + 0.5) * k;
    }

    private long split(int normalizedValue) {
        long maxMask = 0x000000007fffffffL;
        long x = ((long) normalizedValue) & maxMask;
        x = (x ^ (x << 32)) & 0x00000000ffffffffL;
        x = (x ^ (x << 16)) & 0x0000ffff0000ffffL;
        x = (x ^ (x << 8)) & 0x00ff00ff00ff00ffL;
        x = (x ^ (x << 4)) & 0x0f0f0f0f0f0f0f0fL;
        x = (x ^ (x << 2)) & 0x3333333333333333L;
        x = (x ^ (x << 1)) & 0x5555555555555555L;
        return x;
    }

    private int combine(long z) {
        long x = z & 0x5555555555555555L;
        x = (x ^ (x >> 1)) & 0x3333333333333333L;
        x = (x ^ (x >> 2)) & 0x0f0f0f0f0f0f0f0fL;
        x = (x ^ (x >> 4)) & 0x00ff00ff00ff00ffL;
        x = (x ^ (x >> 8)) & 0x0000ffff0000ffffL;
        x = (x ^ (x >> 16)) & 0x00000000ffffffffL;
        return (int) x;
    }

    private long index(double x, double y) {
        int precision = 31;
        long d0 = split(normalize(x, -180.0, 180.0, precision));
        long d1 = split(normalize(y, -90.0, 90.0, precision));
        return d0 | (d1 << 1);
    }

    private double[] invert(long z) {
        int precision = 31;
        double d0 = denormalize(combine(z), -180.0, 180.0, precision);
        double d1 = denormalize(combine(z >> 1), -90.0, 90.0, precision);
        return new double[]{d0, d1};
    }

}
